//
// Created by LiuYou on 2021/8/7.
//

#include <cstdio>
#include <cstdlib>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/syslog.h>
#include <cstring>
#include <cerrno>
#include <csignal>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include "Server.hpp"
#include "../include/Protocol.hpp"
#include "MediaLib.hpp"
#include "ThreadList.hpp"
#include "ThreadChannel.hpp"
#include "TBF.hpp"


// 不加static修饰的全局变量多数会声明到相应的 *.hpp文件中。
//using NetRadio::Server::ServerConfig;
// TODO 注意: 问题2造成的真正原因: serverConfig这个全局对象不能在文件间共享。
struct NetRadio::Server::ServerConfig serverConfig{
        .rcvPort=NetRadio::DEFAULT_RCV_PORT,
        .multiGroup=const_cast<char*>(NetRadio::DEFAULT_MULTI_GROUP),
        // todo 注意: 问题2造成的原因
//        .mediaDir=NetRadio::DEFAULT_MEDIA_DIR,
        .mediaDir=NetRadio::DEFAULT_MEDIA_DIR,
        .runMode=NetRadio::RUN_DAEMON,
        .ifName=const_cast<char*>(NetRadio::DEFAULT_IF)
};


int socketFd;


// TODO 注意: !!! 这里的 extern 对象 senderAddress 在实现文件中的定义有一些受挫。
struct sockaddr_in senderAddress;


using NetRadio::Server::MediaLib::MediaLibEntry;
static struct NetRadio::Server::MediaLib::MediaLibEntry* mediaLibEntryList;


static void printHelp() {
    printf( "-M      指定多播组\n"
            "-P      指定接受端口\""
            "-F      前台运行\n"
            "-D      指定媒体库位置\n"
            "-I      指定网络设备(专门走哪个网卡)\n"
            "-H      显示帮助\n" );
}


static void daemonExit(int s) {
    NetRadio::Server::ThreadList::threadListDestroy();
    NetRadio::Server::ThreadChannel::threadChannelDestroyAll();
    NetRadio::Server::MediaLib::freeChannelList( mediaLibEntryList );

    syslog( LOG_WARNING, "signal-%d caught, exit now.", s );

    closelog();
    exit( 0 );
}


static int daemonize() {
    pid_t pid = fork();
    if (pid < 0) {
//        perror( "fork()" );
        // 写系统日志
        syslog( LOG_ERR, "fork(): %s", strerror(errno));
        return -1;
    }
    if (pid > 0) {
        exit( 0 );
    }
    int fd = open( "/dev/null", O_RDWR );
    if (fd < 0) {
//        perror( "open()" );
        syslog( LOG_WARNING, "open(): %s", strerror(errno));
        return -2;
    } else {
        dup2( fd, 0 );
        dup2( fd, 1 );
        dup2( fd, 2 );
        if (fd > 2) {
            close( fd );
        }
    }

    setsid();

    chdir( "/" );
    umask( 0 );

    return 0;
}


static int socketInit() {
    socketFd = socket( AF_INET, SOCK_DGRAM, 0 );
    if (socketFd < 0) {
        syslog( LOG_ERR, "socket(): %s", strerror(errno));
        exit( 1 );
    }


    struct ip_mreq ipMultiRequest;
    inet_pton( AF_INET, serverConfig.multiGroup, &ipMultiRequest.imr_multiaddr );
    inet_pton( AF_INET, "0.0.0.0", &ipMultiRequest.imr_interface );
    // TODO 注意: !!! 现在man手册中没有 imr_ifindex 这个字段了。
//    ipMultiRequest.imr_ifindex = if_nametoindex( serverConfig.ifName );
    if (setsockopt( socketFd, IPPROTO_IP, IP_MULTICAST_IF,
                    &ipMultiRequest, sizeof( ipMultiRequest )) < 0) {
        syslog( LOG_ERR, "setsockopt( , , IP_MULTICAST_IF, , ): %s", strerror(errno));
        exit( 1 );
    }

//    bind();

    senderAddress.sin_family = AF_INET;
    senderAddress.sin_port = htons( atoi( serverConfig.rcvPort ));
    inet_pton( AF_INET, serverConfig.multiGroup, &senderAddress.sin_addr.s_addr );

    return 0;

}


/// \brief <p>
/// -M      指定多播组 <p>
/// -P      指定接受端口 <p>
/// -F      前台运行 <p>
/// -D      指定媒体库位置 <p>
/// -I      指定网络设备(专门走哪个网卡) <p>
/// -H      显示帮助 <p>
///  <p>
/// \param argc
/// \param argv
/// \return
int main(int argc, char** argv) {
    // 信号处理函数处理守护进程的异常终止
    struct sigaction sigAction;
    sigAction.sa_handler = daemonExit;
    sigemptyset( &sigAction.sa_mask );
    sigaddset( &sigAction.sa_mask, SIGINT );
    sigaddset( &sigAction.sa_mask, SIGQUIT );
    sigaddset( &sigAction.sa_mask, SIGTERM );
    sigaction( SIGTERM, &sigAction, nullptr );
    sigaction( SIGINT, &sigAction, nullptr );
    sigaction( SIGQUIT, &sigAction, nullptr );

    // 写系统日志
    openlog( "netradio", LOG_PID | LOG_PERROR, LOG_DAEMON);


    // 命令行分析
    while (true) {
        int opt = getopt( argc, argv, "M:P:FD:I:H" );
        if (opt < 0) {
            break;
        }
        switch (opt) {
            case 'M': {
                serverConfig.multiGroup = optarg;
                break;
            }
            case 'P': {
                serverConfig.rcvPort = optarg;
                break;
            }
            case 'F': {
                serverConfig.runMode = NetRadio::RUN_FOREGROUND;
                break;
            }
            case 'D': {
                serverConfig.mediaDir = optarg;
                break;
            }
            case 'I': {
                serverConfig.ifName = optarg;
                break;
            }
            case 'H': {
                printHelp();
                exit( 0 );
                break;
            }
            default: {
                abort();
                break;
            }
        }
    }

    // 守护进程的实现(将当前进程做成守护进程)
    if (serverConfig.runMode == NetRadio::RUN_DAEMON) {
        if (daemonize() != 0) {
            exit( 1 );
        }
    } else if (serverConfig.runMode == NetRadio::RUN_FOREGROUND) {
        // do nothing
    } else {
//        fprintf( stderr, "EINVAL\n" );
        syslog( LOG_ERR, "serverConfig.runMode EINVAL" );
        exit( 1 );
    }

    // socket初始化
    socketInit();

    // 获取频道信息
    using NetRadio::Server::MediaLib::getChannelList;
    int listSize;
    // TODO 注意: 调试时错误 2
    int err = getChannelList( &mediaLibEntryList, &listSize );
    if (err) {
        syslog( LOG_ERR, "getChannelList(): %s.", strerror( err ));
        exit( 1 );
    }

    // 创建节目单线程
    using NetRadio::Server::ThreadList::threadListCreate;
    using NetRadio::Server::ThreadList::threadListDestroy;
    err = threadListCreate( mediaLibEntryList, listSize );
    if (err) {
        exit( 1 );
    }

    // 创建频道线程
    using NetRadio::Server::ThreadChannel::threadChannelCreate;
    int i;
    for (i = 0; i < listSize; ++i) {
        int ret = threadChannelCreate( mediaLibEntryList + i );
        if (ret) {
            fprintf( stderr, "threadChannelCreate(): %s\n", strerror( err ));
            exit( 1 );
        }
    }

    syslog( LOG_DEBUG, "%d channel threads created.", i );

    while (true) {
        // 加 pause();的原因: 这样让程序不至于结束, 也不至于时时刻刻都在消耗资源。
        pause();
    }


    exit( 0 );
}


/*
[liuyou@localhost server]$ g++ *.cpp ../include/*.cpp -pthread  -o Server
MediaLib.cpp: 在函数‘NetRadio::Server::MediaLib::ChannelContext* NetRadio::Server::MediaLib::pathToEntry(char*)’中:
MediaLib.cpp:47:24: 警告：‘char* strncat(char*, const char*, size_t)’ specified bound 1024 equals destination size [-Wstringop-overflow=]
                 strncat( pathString, "/desc.text", PATH_SIZE );
                 ~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
MediaLib.cpp: 在函数‘int NetRadio::Server::MediaLib::openNext(NetRadio::ChannelId)’中:
MediaLib.cpp:154:13: 警告：在有返回值的函数中，控制流程到达函数尾 [-Wreturn-type]
             }
             ^
ThreadChannel.cpp: 在函数‘int NetRadio::Server::ThreadChannel::threadChannelDestroy(NetRadio::Server::MediaLib::MediaLibEntry*)’中:
ThreadChannel.cpp:97:1: 警告：在有返回值的函数中，控制流程到达函数尾 [-Wreturn-type]
 }
 ^

// TODO 注意: !!! 这个错误不好找出来, 问题是命名空间的外部变量问题。
/tmp/ccCZvJn8.o：在函数‘NetRadio::Server::MediaLib::getChannelList(NetRadio::Server::MediaLib::MediaLibEntry**, int*)’中：
MediaLib.cpp:(.text+0x227)：对‘NetRadio::Server::serverConfig’未定义的引用
collect2: 错误：ld 返回 1
*/
